#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
花生tg群发软件
支持批量发送消息到多个用户或群组
"""

import asyncio
import json
import os
import sys
from datetime import datetime
from typing import List, Dict, Optional
import logging
import queue
import random

# Telegram相关库
from telethon import TelegramClient, events, functions
from telethon.tl.types import User, Chat, Channel
from telethon.errors import FloodWaitError, SessionPasswordNeededError

# 用户界面相关
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, scrolledtext, simpledialog
import threading

# 卡密验证相关
# from license_manager import LicenseManager
# from license_dialog import LicenseDialog

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('telegram_sender.log', encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

class ToolTip:
    """工具提示类"""
    def __init__(self, widget, text):
        self.widget = widget
        self.text = text
        self.tooltip = None
        self.widget.bind('<Enter>', self.show_tooltip)
        self.widget.bind('<Leave>', self.hide_tooltip)
        
    def show_tooltip(self, event=None):
        x, y, _, _ = self.widget.bbox("insert")
        x += self.widget.winfo_rootx() + 25
        y += self.widget.winfo_rooty() + 20
        
        self.tooltip = tk.Toplevel(self.widget)
        self.tooltip.wm_overrideredirect(True)
        self.tooltip.wm_geometry(f"+{x}+{y}")
        
        label = tk.Label(self.tooltip, text=self.text, 
                        justify=tk.LEFT,
                        background="#ffffe0", 
                        relief=tk.SOLID, 
                        borderwidth=1,
                        font=("Arial", 9))
        label.pack()
        
    def hide_tooltip(self, event=None):
        if self.tooltip:
            self.tooltip.destroy()
            self.tooltip = None

class TelegramMassSender:
    """花生tg群发软件主类"""
    
    def __init__(self):
        # 卡密验证管理器（暂时跳过）
        # self.license_manager = LicenseManager()
        
        # 创建主窗口
        self.root = tk.Tk()
        self.root.title("花生tg群发软件")
        self.root.geometry("800x600")  # 调整初始高度
        
        # 暂时跳过卡密验证
        # if not self.verify_license():
        #     sys.exit(0)
        
        # Telegram API配置
        self.api_id = None
        self.api_hash = None
        self.client = None
        self.is_connected = False
        
        # 设置软件图标
        self.set_window_icon()
        
        # 检查并加载配置
        self.load_or_create_config()
        
        # 初始化界面
        self.init_ui()
        
        # 初始化数据
        self.contacts = []
        self.groups = []
        # 初始化群组数量
        self.group_count = 0
        self.async_loop = None
        self.async_thread = None
        
        # 批量加入群组相关变量
        self.imported_group_links = []  # 导入的群组链接列表
        self.is_joining_groups = False  # 是否正在加入群组
        self.join_group_task = None  # 加入群组任务
        self.pause_joining_groups = False  # 是否暂停加群
        
        # 发送状态控制
        self.is_sending = False
        self.cancel_sending = False
        
        # 定时发送状态控制
        self.scheduled_tasks = []  # 存储定时任务
        self.is_scheduling = False  # 是否有定时任务在运行
        
        # 启动定期验证（暂时跳过）
        # self.license_manager.start_periodic_verification(self.on_license_expired)
    
    def verify_license(self):
        """验证卡密（暂时跳过）"""
        # 检查本地许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        
        # if is_valid:
        #     # 许可证有效，显示信息
        #     license_info = self.license_manager.get_license_info()
        #     if license_info:
        #         messagebox.showinfo("许可证信息", 
        #                           f"许可证有效！\n\n激活码：{license_info.get('license_key', '未知')}\n过期时间：{license_info.get('expire_date', '未知')}")
        #     return True
        
        # # 许可证无效，显示激活对话框
        # dialog = LicenseDialog(self.root, self.license_manager)
        # if dialog.result:
        #     return True
        # else:
        #     return False
        
        # 暂时返回True，跳过验证
        return True
    
    def get_proxy_config(self):
        """获取代理配置"""
        try:
            # 方法1: 检查系统代理设置
            import os
            http_proxy = os.environ.get('HTTP_PROXY') or os.environ.get('http_proxy')
            https_proxy = os.environ.get('HTTPS_PROXY') or os.environ.get('https_proxy')
            
            if http_proxy:
                # 解析代理地址
                from urllib.parse import urlparse
                parsed = urlparse(http_proxy)
                if parsed.scheme in ['http', 'https']:
                    return {
                        'proxy_type': 'http',
                        'addr': parsed.hostname,
                        'port': parsed.port or 80,
                        'username': parsed.username,
                        'password': parsed.password
                    }
            
            # 方法2: 检查v2rayN默认端口
            # v2rayN默认HTTP代理端口是10809，SOCKS代理端口是10808
            v2ray_ports = [10809, 10808]
            for port in v2ray_ports:
                try:
                    import socket
                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    sock.settimeout(1)
                    result = sock.connect_ex(('127.0.0.1', port))
                    sock.close()
                    if result == 0:
                        # 端口开放，尝试使用HTTP代理
                        proxy_type = 'http' if port == 10809 else 'socks5'
                        return {
                            'proxy_type': proxy_type,
                            'addr': '127.0.0.1',
                            'port': port
                        }
                except:
                    continue
            
            # 方法3: 检查配置文件中的代理设置
            config_file = "telegram_config.json"
            if os.path.exists(config_file):
                try:
                    with open(config_file, 'r', encoding='utf-8') as f:
                        config = json.load(f)
                        if 'proxy' in config:
                            return config['proxy']
                except:
                    pass
            
            return None
            
        except Exception as e:
            self.log_message(f"获取代理配置时出错: {e}")
            return None
    
    def on_license_expired(self, is_valid, message):
        """许可证过期回调"""
        if not is_valid:
            # 在主线程中显示过期提示
            self.root.after(0, lambda: self.show_license_expired_dialog(message))
    
    def show_license_expired_dialog(self, message):
        """显示许可证过期对话框"""
        result = messagebox.askyesno("许可证过期", 
                                   f"许可证已过期：{message}\n\n是否重新激活？\n\n选择'否'将退出软件。")
        
        if result:
            # 重新激活
            if self.verify_license():
                return
            else:
                sys.exit(0)
        else:
            # 退出软件
            sys.exit(0)
        
    def set_window_icon(self):
        """设置窗口图标"""
        # 支持的图标文件格式
        icon_extensions = ['.ico', '.png', '.gif', '.bmp']
        icon_found = False
        
        def get_resource_path(relative_path):
            """获取资源文件的绝对路径"""
            try:
                # PyInstaller创建临时文件夹，将路径存储在_MEIPASS中
                base_path = sys._MEIPASS
            except Exception:
                base_path = os.path.abspath(".")
            return os.path.join(base_path, relative_path)
        
        # 尝试加载图标文件
        for ext in icon_extensions:
            icon_path = f"icon{ext}"
            resource_path = get_resource_path(icon_path)
            if os.path.exists(resource_path):
                try:
                    # 对于不同平台使用不同的方法
                    if sys.platform.startswith('win'):
                        # Windows平台
                        if ext.lower() == '.ico':
                            # ICO格式直接使用iconbitmap
                            self.root.iconbitmap(resource_path)
                        else:
                            # 其他格式使用PhotoImage
                            try:
                                from PIL import Image, ImageTk
                                img = Image.open(resource_path)
                                # 调整尺寸为32x32
                                img = img.resize((32, 32), Image.Resampling.LANCZOS)
                                photo = ImageTk.PhotoImage(img)
                                self.root.iconphoto(True, photo)
                                # 保持引用防止垃圾回收
                                self.icon_photo = photo
                            except ImportError:
                                print("PIL/Pillow库未安装，无法加载PNG图标")
                                print("请安装: pip install Pillow")
                                continue
                            except Exception as e:
                                print(f"Windows PNG图标加载失败: {e}")
                                continue
                    elif sys.platform.startswith('darwin'):
                        # macOS平台 - 尝试多种方法
                        try:
                            # 方法1: 直接设置
                            self.root.iconbitmap(resource_path)
                        except:
                            try:
                                # 方法2: 使用PhotoImage (适用于PNG)
                                if ext.lower() == '.png':
                                    from PIL import Image, ImageTk
                                    img = Image.open(resource_path)
                                    # 调整尺寸为32x32
                                    img = img.resize((32, 32), Image.Resampling.LANCZOS)
                                    photo = ImageTk.PhotoImage(img)
                                    self.root.iconphoto(True, photo)
                                    # 保持引用防止垃圾回收
                                    self.icon_photo = photo
                                else:
                                    self.root.iconbitmap(resource_path)
                            except Exception as e2:
                                print(f"macOS图标加载方法2失败: {e2}")
                                # 方法3: 尝试使用PhotoImage不调整尺寸
                                try:
                                    from PIL import Image, ImageTk
                                    img = Image.open(resource_path)
                                    photo = ImageTk.PhotoImage(img)
                                    self.root.iconphoto(True, photo)
                                    self.icon_photo = photo
                                except Exception as e3:
                                    print(f"macOS图标加载方法3失败: {e3}")
                                    continue
                    else:
                        # Linux平台
                        self.root.iconbitmap(resource_path)
                    icon_found = True
                    print(f"成功加载图标: {icon_path}")
                    break
                except Exception as e:
                    print(f"加载图标 {icon_path} 失败: {e}")
                    continue
        
        if not icon_found:
            print("未找到图标文件，使用默认图标")
            print("支持的图标文件格式: .ico, .png, .gif, .bmp")
            print("请将图标文件命名为 'icon' 并放在程序同目录下")
            print("例如: icon.ico, icon.png, icon.gif, icon.bmp")
            print("\n对于macOS用户:")
            print("- PNG格式图标可能需要安装PIL/Pillow库: pip install Pillow")
            print("- 建议使用ICO格式图标以获得最佳效果")
            print("- 图标尺寸建议为32x32或64x64像素")
        
    def load_or_create_config(self):
        """加载或创建配置文件"""
        config_file = "telegram_config.json"
        # 检查配置文件是否存在
        if os.path.exists(config_file):
            try:
                with open(config_file, 'r', encoding='utf-8') as f:
                    config = json.load(f)
                    self.api_id = config.get('api_id')
                    self.api_hash = config.get('api_hash')
                    if self.api_id and self.api_hash:
                        return  # 配置已存在且有效
            except Exception as e:
                print(f"读取配置文件失败: {e}")
        # 如果配置不存在或无效，弹窗输入
        self.show_api_config_dialog()

    def show_api_config_dialog(self):
        """显示API配置输入弹窗"""
        config_window = tk.Toplevel(self.root)
        config_window.title("Telegram API配置")
        config_window.geometry("400x240")
        config_window.resizable(False, False)
        config_window.transient(self.root)
        config_window.grab_set()
        config_window.focus_set()
        # 居中显示
        config_window.update_idletasks()
        x = (config_window.winfo_screenwidth() // 2) - (400 // 2)
        y = (config_window.winfo_screenheight() // 2) - (240 // 2)
        config_window.geometry(f"400x240+{x}+{y}")
        # 主框架
        main_frame = tk.Frame(config_window, bg='#f0f0f0')
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
        # 标题
        title_label = tk.Label(main_frame, text="首次使用配置", font=("Arial", 15, "bold"), bg='#f0f0f0', fg='#333333')
        title_label.pack(pady=(0, 10))
        # 说明文字
        desc_label = tk.Label(main_frame, text="请填写您的Telegram API配置信息", font=("Arial", 10), bg='#f0f0f0', fg='#666666', justify=tk.LEFT)
        desc_label.pack(pady=(0, 10))
        # 输入框架
        input_frame = tk.Frame(main_frame, bg='#f0f0f0')
        input_frame.pack(fill=tk.X, pady=(0, 10))
        # API ID输入
        tk.Label(input_frame, text="API ID:", font=("Arial", 11), bg='#f0f0f0').grid(row=0, column=0, sticky=tk.W, pady=5)
        api_id_var = tk.StringVar()
        api_id_entry = tk.Entry(input_frame, textvariable=api_id_var, width=28, font=("Arial", 11))
        api_id_entry.grid(row=0, column=1, padx=(10, 0), pady=5)
        # API Hash输入
        tk.Label(input_frame, text="API Hash:", font=("Arial", 11), bg='#f0f0f0').grid(row=1, column=0, sticky=tk.W, pady=5)
        api_hash_var = tk.StringVar()
        api_hash_entry = tk.Entry(input_frame, textvariable=api_hash_var, width=28, font=("Arial", 11), show="*")
        api_hash_entry.grid(row=1, column=1, padx=(10, 0), pady=5)
        # 显示/隐藏API Hash按钮
        def toggle_hash_visibility():
            if api_hash_entry.cget('show') == '*':
                api_hash_entry.config(show='')
                toggle_btn.config(text="隐藏")
            else:
                api_hash_entry.config(show='*')
                toggle_btn.config(text="显示")
        toggle_btn = tk.Button(input_frame, text="显示", command=toggle_hash_visibility, font=("Arial", 9), bg='#e9ecef', relief=tk.FLAT)
        toggle_btn.grid(row=1, column=2, padx=(5, 0), pady=5)
        # 按钮框架
        button_frame = tk.Frame(main_frame, bg='#f0f0f0')
        button_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=(10, 0))
        def save_config():
            api_id = api_id_var.get().strip()
            api_hash = api_hash_var.get().strip()
            if not api_id or not api_hash:
                messagebox.showerror("错误", "请填写完整的API配置信息", parent=config_window)
                return
            try:
                int(api_id)
            except ValueError:
                messagebox.showerror("错误", "API ID必须是数字", parent=config_window)
                return
            self.api_id = int(api_id)
            self.api_hash = api_hash
            config = {'api_id': self.api_id, 'api_hash': self.api_hash}
            try:
                with open("telegram_config.json", 'w', encoding='utf-8') as f:
                    json.dump(config, f, ensure_ascii=False, indent=2)
                messagebox.showinfo("成功", "API配置已保存", parent=config_window)
                config_window.grab_release()
                config_window.destroy()
            except Exception as e:
                messagebox.showerror("错误", f"保存配置失败: {e}", parent=config_window)
        # 确定按钮
        ok_button = tk.Button(button_frame, text="确定", command=save_config, font=("Arial", 12, "bold"), bg='#4CAF50', fg='white', relief=tk.FLAT, bd=0, width=12, height=1)
        ok_button.pack(side=tk.RIGHT, padx=(10, 0))
        # 取消按钮
        cancel_button = tk.Button(button_frame, text="取消", command=lambda: sys.exit(0), font=("Arial", 12), bg='#f8f9fa', fg='#6c757d', relief=tk.FLAT, bd=1, width=8, height=1)
        cancel_button.pack(side=tk.RIGHT)
        api_id_entry.focus_set()
        config_window.bind('<Return>', lambda e: save_config())
        config_window.bind('<Escape>', lambda e: sys.exit(0))
        config_window.wait_window()
        if not self.api_id or not self.api_hash:
            sys.exit(0)
        
    def init_ui(self):
        """初始化图形用户界面"""
        # 状态栏
        status_frame = ttk.Frame(self.root)
        status_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(status_frame, text="连接状态:", font=("Arial", 10, "bold")).pack(side=tk.LEFT)
        self.status_label = ttk.Label(status_frame, text="未连接")
        self.status_label.pack(side=tk.LEFT, padx=(5, 0))
        self.connect_btn = ttk.Button(status_frame, text="连接Telegram", command=self.connect_telegram)
        self.connect_btn.pack(side=tk.RIGHT)

        # 创建Notebook（Tab控件）
        self.notebook = ttk.Notebook(self.root)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        # ========== 消息群发Tab ==========
        mass_tab = ttk.Frame(self.notebook)
        self.notebook.add(mass_tab, text="消息群发")

        # 消息Tab顶部按钮区
        msg_button_frame = ttk.Frame(mass_tab)
        msg_button_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(msg_button_frame, text="获取联系人", command=self.get_contacts).grid(row=0, column=0, padx=5)
        ttk.Button(msg_button_frame, text="获取群组", command=self.get_groups).grid(row=0, column=1, padx=5)

        # 消息发送区域 - 左右分栏
        message_frame = ttk.LabelFrame(mass_tab, text="消息发送", padding="5")
        message_frame.pack(fill=tk.X, padx=10, pady=5)
        left_frame = ttk.Frame(message_frame)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 10))
        ttk.Label(left_frame, text="消息内容:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.message_text = scrolledtext.ScrolledText(left_frame, height=4, width=40)
        self.message_text.grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        ttk.Label(left_frame, text="发送间隔(秒):").grid(row=1, column=0, sticky=tk.W, pady=5)
        # 修改为区间输入
        interval_frame = ttk.Frame(left_frame)
        interval_frame.grid(row=1, column=1, sticky=tk.W, pady=5)
        self.interval_var_min = tk.StringVar(value="2")
        self.interval_var_max = tk.StringVar(value="")
        ttk.Entry(interval_frame, textvariable=self.interval_var_min, width=5).pack(side=tk.LEFT)
        ttk.Label(interval_frame, text="—").pack(side=tk.LEFT, padx=2)
        ttk.Entry(interval_frame, textvariable=self.interval_var_max, width=5).pack(side=tk.LEFT)
        ToolTip(interval_frame, "设置发送消息的间隔时间，可以设置单个值或区间值（如：2-5），单位：秒")
        send_button_frame = ttk.Frame(left_frame)
        send_button_frame.grid(row=1, column=2, padx=5, pady=5)
        self.send_button = ttk.Button(send_button_frame, text="发送消息", command=self.send_messages)
        self.send_button.pack(side=tk.LEFT, padx=(0, 5))
        self.cancel_button = ttk.Button(send_button_frame, text="取消发送", command=self.cancel_send, state=tk.DISABLED)
        self.cancel_button.pack(side=tk.LEFT)
        right_frame = ttk.Frame(message_frame)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        ttk.Label(right_frame, text="多少分钟后:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.schedule_minutes_var = tk.StringVar(value="5.00")
        self.schedule_minutes_entry = ttk.Entry(right_frame, textvariable=self.schedule_minutes_var, width=10)
        self.schedule_minutes_entry.grid(row=0, column=1, sticky=tk.W, pady=5)
        ToolTip(self.schedule_minutes_entry, "定时发送消息的时间，以分钟为单位，可以小数点")
        schedule_button_frame = ttk.Frame(right_frame)
        schedule_button_frame.grid(row=1, column=0, columnspan=2, pady=5)
        self.schedule_button = ttk.Button(schedule_button_frame, text="定时发送", command=self.schedule_send)
        self.schedule_button.pack(side=tk.LEFT, padx=(0, 5))
        self.cancel_schedule_button = ttk.Button(schedule_button_frame, text="取消定时任务", command=self.cancel_schedule_send, state=tk.DISABLED)
        self.cancel_schedule_button.pack(side=tk.LEFT)
        schedule_list_frame = ttk.LabelFrame(mass_tab, text="定时任务列表", padding="5")
        schedule_list_frame.pack(fill=tk.X, padx=10, pady=5)
        schedule_columns = ('checkbox', 'time', 'message', 'groups', 'status')
        self.schedule_tree = ttk.Treeview(schedule_list_frame, columns=schedule_columns, show='headings', height=4)
        self.schedule_tree.heading('checkbox', text='选择')
        self.schedule_tree.heading('time', text='发送时间')
        self.schedule_tree.heading('message', text='消息内容')
        self.schedule_tree.heading('groups', text='目标群组')
        self.schedule_tree.heading('status', text='状态')
        self.schedule_tree.column('checkbox', width=50, anchor='center')
        self.schedule_tree.column('time', width=150)
        self.schedule_tree.column('message', width=200)
        self.schedule_tree.column('groups', width=100)
        self.schedule_tree.column('status', width=80)
        schedule_scrollbar = ttk.Scrollbar(schedule_list_frame, orient=tk.HORIZONTAL, command=self.schedule_tree.xview)
        self.schedule_tree.configure(xscrollcommand=schedule_scrollbar.set)
        self.schedule_tree.pack(side=tk.TOP, fill=tk.X)
        schedule_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
        self.schedule_tree.bind("<Button-1>", self.on_schedule_tree_click)
        # 联系人列表
        list_frame = ttk.Frame(mass_tab)
        list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        title_frame = ttk.Frame(list_frame)
        title_frame.pack(fill=tk.X, pady=(0, 5))
        left_title = ttk.Label(title_frame, text="联系人列表", font=("Arial", 10, "bold"))
        left_title.pack(side=tk.LEFT)
        self.group_count_label = ttk.Label(title_frame, text="群组数量: 0", font=("Arial", 10))
        self.group_count_label.pack(side=tk.RIGHT)
        list_content_frame = ttk.Frame(list_frame)
        list_content_frame.pack(fill=tk.BOTH, expand=True)
        columns = ('checkbox', 'name', 'username', 'phone', 'type', 'id')
        self.contact_tree = ttk.Treeview(list_content_frame, columns=columns, show='headings', height=15)
        self.contact_tree.heading('checkbox', text='选择')
        self.contact_tree.heading('name', text='姓名')
        self.contact_tree.heading('username', text='用户名')
        self.contact_tree.heading('phone', text='电话')
        self.contact_tree.heading('type', text='类型')
        self.contact_tree.heading('id', text='ID')
        self.contact_tree.column('checkbox', width=100, anchor='center')
        self.contact_tree.column('name', width=250)
        self.contact_tree.column('username', width=150)
        self.contact_tree.column('phone', width=120)
        self.contact_tree.column('type', width=80)
        self.contact_tree.column('id', width=0)
        scrollbar = ttk.Scrollbar(list_content_frame, orient=tk.VERTICAL, command=self.contact_tree.yview)
        self.contact_tree.configure(yscrollcommand=scrollbar.set)
        self.contact_tree.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        # 让Treeview自动填满整个list_content_frame，消除下方空白
        list_content_frame.rowconfigure(0, weight=1)
        list_content_frame.columnconfigure(0, weight=1)
        self.context_menu = tk.Menu(self.contact_tree, tearoff=0)
        self.context_menu.add_command(label="删除选中", command=self.delete_selected)
        self.context_menu.add_command(label="全选", command=self.select_all)
        self.context_menu.add_command(label="取消全选", command=self.unselect_all)
        self.contact_tree.bind("<Button-3>", self.show_context_menu)
        self.contact_tree.bind("<Button-1>", self.on_tree_click)

        # ========== 群组管理Tab ==========
        group_tab = ttk.Frame(self.notebook)
        self.notebook.add(group_tab, text="群组管理")
        # 参数输入card
        param_card = ttk.LabelFrame(group_tab, text="加群参数设置", padding="10")
        param_card.pack(fill=tk.X, padx=20, pady=(20, 10))
        # 每隔(秒)
        ttk.Label(param_card, text="每隔(秒):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        self.join_interval_var_min = tk.StringVar(value="5")
        self.join_interval_var_max = tk.StringVar(value="")
        ttk.Entry(param_card, textvariable=self.join_interval_var_min, width=5).grid(row=0, column=1, padx=(5,0), pady=5)
        ttk.Label(param_card, text="—").grid(row=0, column=2, padx=(2,2), pady=5)
        ttk.Entry(param_card, textvariable=self.join_interval_var_max, width=5).grid(row=0, column=3, padx=(0,5), pady=5)
        # 每组(条)
        ttk.Label(param_card, text="每组(条):").grid(row=0, column=4, padx=5, pady=5, sticky=tk.W)
        self.join_batch_size_var_min = tk.StringVar(value="5")
        self.join_batch_size_var_max = tk.StringVar(value="")
        ttk.Entry(param_card, textvariable=self.join_batch_size_var_min, width=5).grid(row=0, column=5, padx=(5,0), pady=5)
        ttk.Label(param_card, text="—").grid(row=0, column=6, padx=(2,2), pady=5)
        ttk.Entry(param_card, textvariable=self.join_batch_size_var_max, width=5).grid(row=0, column=7, padx=(0,5), pady=5)
        # 组间休息(分钟)
        ttk.Label(param_card, text="组间休息(分钟):").grid(row=0, column=8, padx=5, pady=5, sticky=tk.W)
        self.join_batch_pause_var_min = tk.StringVar(value="3")
        self.join_batch_pause_var_max = tk.StringVar(value="")
        ttk.Entry(param_card, textvariable=self.join_batch_pause_var_min, width=5).grid(row=0, column=9, padx=(5,0), pady=5)
        ttk.Label(param_card, text="—").grid(row=0, column=10, padx=(2,2), pady=5)
        ttk.Entry(param_card, textvariable=self.join_batch_pause_var_max, width=5).grid(row=0, column=11, padx=(0,5), pady=5)
        # 按钮组
        btn_group = ttk.Frame(group_tab)
        btn_group.pack(fill=tk.X, padx=20, pady=(0, 10))
        # 新增频繁等待CheckBox
        self.wait_flood_var = tk.BooleanVar(value=False)
        self.wait_flood_checkbox = ttk.Checkbutton(btn_group, text="频繁等待", variable=self.wait_flood_var)
        self.wait_flood_checkbox.pack(side=tk.LEFT, padx=(0, 5), ipadx=5, ipady=3)
        self.import_links_button = ttk.Button(btn_group, text="导入群组链接", command=self.import_group_links)
        self.import_links_button.pack(side=tk.LEFT, padx=10, ipadx=10, ipady=3)
        self.cancel_join_button = ttk.Button(btn_group, text="取消加入", command=self.cancel_join_groups, state=tk.DISABLED)
        self.cancel_join_button.pack(side=tk.LEFT, padx=10, ipadx=10, ipady=3)
        # 新增暂停/开始按钮
        self.pause_join_button = ttk.Button(btn_group, text="暂停", command=self.toggle_pause_join, state=tk.DISABLED)
        self.pause_join_button.pack(side=tk.LEFT, padx=10, ipadx=10, ipady=3)
        # 新增统计群组数量按钮
        self.stat_group_count_button = ttk.Button(btn_group, text="统计群组数量", command=self.stat_group_count)
        self.stat_group_count_button.pack(side=tk.LEFT, padx=10, ipadx=10, ipady=3)

        # ========== 操作日志Tab ==========
        log_tab = ttk.Frame(self.notebook)
        self.notebook.add(log_tab, text="操作日志")
        
        # 日志标题和导出按钮同行
        log_title_frame = ttk.Frame(log_tab)
        log_title_frame.pack(fill=tk.X, padx=10, pady=(5, 0))
        log_title_label = ttk.Label(log_title_frame, text="操作日志", font=("Arial", 10, "bold"))
        log_title_label.pack(side=tk.LEFT)
        self.export_log_button = ttk.Button(log_title_frame, text="导出日志", command=self.export_logs)
        self.export_log_button.pack(side=tk.RIGHT)
        
        # 日志文本区域
        self.log_text = scrolledtext.ScrolledText(log_tab, height=20, wrap=tk.WORD)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
        
        # 日志右键菜单
        self.log_menu = tk.Menu(self.log_text, tearoff=0)
        self.log_menu.add_command(label="清空日志", command=self.clear_logs)
        self.log_menu.add_separator()
        self.log_menu.add_command(label="复制选中", command=self.copy_selected_logs)
        self.log_text.bind("<Button-3>", self.show_log_menu)

        # 自动调整窗口大小
        self.root.update_idletasks()
        width = 800  # 固定宽度
        height = min(800, self.notebook.winfo_reqheight() + 50)  # 根据内容调整高度，最大800
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2
        self.root.geometry(f"{width}x{height}+{x}+{y}")  # 设置窗口大小和位置

    def on_schedule_tree_click(self, event):
        """处理定时任务列表的点击事件"""
        region = self.schedule_tree.identify("region", event.x, event.y)
        if region == "heading":
            # 点击的是列标题，检查是否是勾选框列
            column = self.schedule_tree.identify_column(event.x)
            if column == "#1":  # 勾选框列
                self.toggle_all_schedule_checkboxes()
            return
            
        # 点击的是数据行
        item = self.schedule_tree.identify_row(event.y)
        if item:
            column = self.schedule_tree.identify_column(event.x)
            if column == "#1":  # 勾选框列
                self.toggle_schedule_checkbox(item)
                
    def toggle_schedule_checkbox(self, item):
        """切换单个定时任务勾选框状态"""
        values = list(self.schedule_tree.item(item)['values'])
        if values[0] == "☐":  # 未选中
            values[0] = "☑"
        else:  # 已选中
            values[0] = "☐"
        self.schedule_tree.item(item, values=values)
        self.update_schedule_button_state()
        
    def toggle_all_schedule_checkboxes(self):
        """切换所有定时任务勾选框状态"""
        children = self.schedule_tree.get_children()
        if not children:
            return
            
        # 检查是否所有项目都已选中
        all_checked = True
        for child in children:
            values = self.schedule_tree.item(child)['values']
            if values[0] != "☑":
                all_checked = False
                break
                
        # 根据当前状态切换
        new_state = "☐" if all_checked else "☑"
        for child in children:
            values = list(self.schedule_tree.item(child)['values'])
            values[0] = new_state
            self.schedule_tree.item(child, values=values)
            
        self.update_schedule_button_state()
        
    def update_schedule_button_state(self):
        """更新定时任务按钮状态"""
        # 检查是否有选中的定时任务
        has_checked = False
        for item in self.schedule_tree.get_children():
            values = self.schedule_tree.item(item)['values']
            if values[0] == "☑":
                has_checked = True
                break
                
        # 更新取消按钮状态
        if has_checked:
            self.cancel_schedule_button.config(state=tk.NORMAL)
        else:
            self.cancel_schedule_button.config(state=tk.DISABLED)
        
    def log_message(self, message: str):
        """添加日志消息"""
        timestamp = datetime.now().strftime("%H:%M:%S")
        log_entry = f"[{timestamp}] {message}\n"
        self.log_text.insert(tk.END, log_entry)
        self.log_text.see(tk.END)
        logger.info(message)
        
    def show_context_menu(self, event):
        """显示右键菜单"""
        self.context_menu.post(event.x_root, event.y_root)
        
    def on_tree_click(self, event):
        """处理树形控件的点击事件"""
        region = self.contact_tree.identify("region", event.x, event.y)
        if region == "heading":
            # 点击的是列标题，检查是否是勾选框列
            column = self.contact_tree.identify_column(event.x)
            if column == "#1":  # 勾选框列
                self.toggle_all_checkboxes()
            return
            
        # 点击的是数据行
        item = self.contact_tree.identify_row(event.y)
        if item:
            column = self.contact_tree.identify_column(event.x)
            if column == "#1":  # 勾选框列
                self.toggle_checkbox(item)
                
    def toggle_checkbox(self, item):
        """切换单个勾选框状态"""
        values = list(self.contact_tree.item(item)['values'])
        if values[0] == "☐":  # 未选中，使用方框
            values[0] = "☑"
        else:  # 已选中，使用打勾符号
            values[0] = "☐"
        self.contact_tree.item(item, values=values)
        
        # 更新群组数量显示
        if hasattr(self, 'groups'):
            self.update_group_count(len(self.groups))
        
    def toggle_all_checkboxes(self):
        """切换所有勾选框状态"""
        children = self.contact_tree.get_children()
        if not children:
            return
            
        # 检查是否所有项目都已选中
        all_checked = True
        for child in children:
            values = self.contact_tree.item(child)['values']
            if values[0] != "☑":
                all_checked = False
                break
                
        # 根据当前状态切换
        new_state = "☐" if all_checked else "☑"
        for child in children:
            values = list(self.contact_tree.item(child)['values'])
            values[0] = new_state
            self.contact_tree.item(child, values=values)
            
        # 更新群组数量显示
        if hasattr(self, 'groups'):
            self.update_group_count(len(self.groups))
        
    def show_log_menu(self, event):
        """显示日志文本框的右键菜单"""
        self.log_menu.post(event.x_root, event.y_root)
        
    def clear_logs(self):
        """清空日志文本框"""
        self.log_text.delete("1.0", tk.END)
        self.log_message("日志已清空")
        
    def copy_selected_logs(self):
        """复制选中的日志"""
        selected_text = self.log_text.selection_get()
        if selected_text:
            self.root.clipboard_clear()
            self.root.clipboard_append(selected_text)
            self.root.update() # 确保剪贴板更新
            self.log_message("已复制选中的日志")
        else:
            messagebox.showwarning("警告", "请先选择要复制的日志")
        
    def connect_telegram(self):
        """连接Telegram"""
        # 检查许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        # if not is_valid:
        #     messagebox.showerror("许可证验证", f"许可证无效：{message}\n请重新激活软件。")
        #     if not self.verify_license():
        #         return
        self.log_message("正在连接Telegram...")
        # 禁用连接按钮，防止重复点击
        self.connect_btn.config(state=tk.DISABLED)
        # 检测代理配置
        proxy_config = self.get_proxy_config()
        if proxy_config:
            self.log_message(f"检测到代理配置: {proxy_config['addr']}:{proxy_config['port']}")
        else:
            self.log_message("未检测到代理配置，将尝试直连")
        # 主线程弹窗，获取手机号
        phone = simpledialog.askstring("手机号", "请输入您的手机号（包含国家代码，如：+8613800138000）：")
        if not phone:
            self.log_message("已取消连接")
            self.connect_btn.config(state=tk.NORMAL)
            return
        # 配置代理设置
        proxy_config = self.get_proxy_config()
        if proxy_config:
            self.log_message(f"使用代理: {proxy_config['addr']}:{proxy_config['port']}")
            # 转换为Telethon期望的代理格式
            try:
                if proxy_config['proxy_type'] == 'http':
                    # 使用HTTP代理
                    proxy = {
                        'proxy_type': 'http',
                        'addr': proxy_config['addr'],
                        'port': proxy_config['port'],
                        'username': proxy_config.get('username'),
                        'password': proxy_config.get('password')
                    }
                else:  # socks5
                    # 使用SOCKS5代理
                    proxy = {
                        'proxy_type': 'socks5',
                        'addr': proxy_config['addr'],
                        'port': proxy_config['port'],
                        'username': proxy_config.get('username'),
                        'password': proxy_config.get('password')
                    }
                # 尝试使用代理连接
                self.client = TelegramClient('session_name', self.api_id, self.api_hash, proxy=proxy)
                self.log_message(f"代理配置成功: {proxy_config['proxy_type']} 代理")
            except Exception as e:
                self.log_message(f"代理配置失败: {e}，尝试直连")
                self.client = TelegramClient('session_name', self.api_id, self.api_hash)
        else:
            self.log_message("未配置代理，使用直连")
            self.client = TelegramClient('session_name', self.api_id, self.api_hash)
        def run_async():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
            loop.run_until_complete(self._do_connect(phone))
            loop.run_forever()
        self.async_thread = threading.Thread(target=run_async, daemon=True)
        self.async_thread.start()
        self.async_loop = None

    def ask_in_main_thread(self, prompt, title):
        """在主线程中显示输入对话框"""
        self.log_message(f"准备显示对话框: {title} - {prompt}")
        result = [None]  # 使用列表来存储结果
        def ask():
            try:
                self.log_message("正在显示对话框...")
                result[0] = simpledialog.askstring(title, prompt)
                self.log_message(f"对话框返回结果: {result[0]}")
            except Exception as e:
                self.log_message(f"输入对话框错误: {e}")
                result[0] = None
        # 确保在主线程中执行
        if threading.current_thread() is threading.main_thread():
            self.log_message("在主线程中直接调用")
            ask()
        else:
            self.log_message("在异步线程中，使用after方法")
            self.root.after(0, ask)
            # 无限等待用户输入
            while result[0] is None:
                self.root.update()
                import time
                time.sleep(0.1)
        return result[0]

    async def _do_connect(self, phone):
        try:
            self.async_loop = asyncio.get_event_loop()
            def get_code():
                return self.ask_in_main_thread("请输入收到的验证码：", "验证码")
            def get_password():
                return self.ask_in_main_thread("请输入两步验证密码（如果有）：", "密码")
            # 直接等待用户输入和网络连接，不设置整体超时
            await self.client.start(
                phone=lambda: phone,
                code_callback=get_code,
                password=get_password
            )
            if self.client.is_connected():
                self.is_connected = True
                self.root.after(0, lambda: self.status_label.config(text="已连接", foreground="green"))
                self.root.after(0, lambda: self.connect_btn.config(text="断开连接"))
                self.root.after(0, lambda: self.connect_btn.config(state=tk.NORMAL))
                self.root.after(0, lambda: self.log_message("Telegram连接成功"))
            else:
                self.root.after(0, lambda: self.log_message("连接失败"))
                self.root.after(0, lambda: self.connect_btn.config(state=tk.NORMAL))
        except Exception as error:
            error_msg = str(error)
            self.root.after(0, lambda: self.log_message(f"连接错误: {error_msg}"))
            self.root.after(0, lambda: self.connect_btn.config(state=tk.NORMAL))

    def get_contacts(self):
        """获取联系人列表"""
        # 检查许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        # if not is_valid:
        #     messagebox.showerror("许可证验证", f"许可证无效：{message}\n请重新激活软件。")
        #     if not self.verify_license():
        #         return
        
        if not self.is_connected:
            messagebox.showerror("错误", "请先连接Telegram")
            return
        
        # 使用已连接的事件循环
        if hasattr(self, 'async_thread') and self.async_thread.is_alive():
            # 通过队列或其他方式发送任务到异步线程
            self.root.after(100, self._schedule_get_contacts)
        else:
            messagebox.showerror("错误", "异步线程未运行")
            
    def _schedule_get_contacts(self):
        """调度获取联系人任务"""
        # 使用已保存的事件循环
        if self.async_loop and not self.async_loop.is_closed():
            future = asyncio.run_coroutine_threadsafe(self._do_get_contacts(), self.async_loop)
        else:
            messagebox.showerror("错误", "事件循环不可用")

    async def _do_get_contacts(self):
        try:
            self.log_message("正在获取联系人...")
            contacts = []
            # 通过 get_dialogs 获取所有对话，筛选出用户
            dialogs = await self.client.get_dialogs()
            for dialog in dialogs:
                # 检查是否为用户对话（不是群组或频道）
                if hasattr(dialog.entity, 'first_name') and not getattr(dialog, 'is_group', False) and not getattr(dialog, 'is_channel', False):
                    entity = dialog.entity
                    if entity.username or getattr(entity, 'phone', None):
                        contacts.append({
                            'id': entity.id,
                            'name': f"{entity.first_name or ''} {getattr(entity, 'last_name', '') or ''}".strip(),
                            'username': entity.username or '',
                            'phone': getattr(entity, 'phone', '') or '',
                            'type': '用户'
                        })
            self.contacts = contacts
            self.root.after(0, self.update_contact_list)
            self.root.after(0, lambda: self.log_message(f"获取到 {len(contacts)} 个联系人"))
        except Exception as error:
            error_msg = str(error)
            self.root.after(0, lambda: self.log_message(f"获取联系人错误: {error_msg}"))

    def get_groups(self):
        """获取群组列表"""
        # 检查许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        # if not is_valid:
        #     messagebox.showerror("许可证验证", f"许可证无效：{message}\n请重新激活软件。")
        #     if not self.verify_license():
        #         return
        
        if not self.is_connected:
            messagebox.showerror("错误", "请先连接Telegram")
            return
        
        # 使用已连接的事件循环
        if hasattr(self, 'async_thread') and self.async_thread.is_alive():
            # 通过队列或其他方式发送任务到异步线程
            self.root.after(100, self._schedule_get_groups)
        else:
            messagebox.showerror("错误", "异步线程未运行")
            
    def _schedule_get_groups(self):
        """调度获取群组任务"""
        # 使用已保存的事件循环
        if self.async_loop and not self.async_loop.is_closed():
            future = asyncio.run_coroutine_threadsafe(self._do_get_groups(), self.async_loop)
        else:
            messagebox.showerror("错误", "事件循环不可用")

    async def _do_get_groups(self):
        try:
            self.log_message("正在获取群组...")
            groups = []
            # 只获取群组，不获取频道
            dialogs = await self.client.get_dialogs()
            for dialog in dialogs:
                if getattr(dialog, 'is_group', False):  # 只获取群组，不获取频道
                    # 添加调试信息
                    group_type = '群组'
                    self.log_message(f"找到群组: {dialog.title}, ID: {dialog.id}, 类型: {group_type}")
                    
                    # 检查是否有发送消息的权限
                    try:
                        # 尝试获取群组的详细信息
                        entity_info = await self.client.get_entity(dialog.id)
                        self.log_message(f"群组实体信息: {type(entity_info).__name__}")
                        
                        # 尝试获取群组成员数量
                        member_count = "未知"
                        try:
                            participants = await self.client.get_participants(entity_info, limit=1)
                            if participants:
                                member_count = "可获取"
                            else:
                                member_count = "0"
                        except Exception as e:
                            member_count = f"无法获取({str(e)[:30]}...)"
                        
                        self.log_message(f"群组 '{dialog.title}' 成员数量: {member_count}")
                        
                        # 获取更多群组信息用于分辨
                        group_info = {
                            'id': dialog.id,
                            'name': dialog.title,
                            'username': getattr(dialog.entity, 'username', '') or '',
                            'phone': '',
                            'type': group_type,
                            'entity': entity_info,  # 保存重新获取的实体对象
                            'member_count': member_count,
                            'dialog_id': dialog.id,  # 对话ID
                            'date': getattr(dialog, 'date', None),  # 最后活动时间
                            'unread_count': getattr(dialog, 'unread_count', 0),  # 未读消息数
                            'is_verified': getattr(dialog.entity, 'verified', False),  # 是否认证
                            'is_scam': getattr(dialog.entity, 'scam', False),  # 是否诈骗
                            'is_fake': getattr(dialog.entity, 'fake', False),  # 是否虚假
                        }
                        
                        # 尝试获取群组描述
                        try:
                            if hasattr(entity_info, 'about'):
                                group_info['description'] = entity_info.about or "无描述"
                            else:
                                group_info['description'] = "无描述"
                        except:
                            group_info['description'] = "无法获取"
                        
                        groups.append(group_info)
                        
                    except Exception as e:
                        self.log_message(f"无法获取群组 {dialog.title} 的详细信息: {e}")
                        # 仍然添加到列表中，但标记为可能无法发送
                        groups.append({
                            'id': dialog.id,
                            'name': dialog.title,
                            'username': getattr(dialog.entity, 'username', '') or '',
                            'phone': '',
                            'type': group_type,
                            'entity': None,  # 标记为无效
                            'member_count': "未知",
                            'dialog_id': dialog.id,
                            'date': getattr(dialog, 'date', None),
                            'unread_count': getattr(dialog, 'unread_count', 0),
                            'is_verified': False,
                            'is_scam': False,
                            'is_fake': False,
                            'description': "无法获取"
                        })
            
            self.groups = groups
            self.root.after(0, self.update_contact_list)
            
            # 更新群组数量显示
            self.root.after(0, lambda: self.update_group_count(len(groups)))
            
            # 显示群聊列表
            if groups:
                self.log_message("📋 群聊列表:")
                for i, group in enumerate(groups, 1):
                    # 构建显示名称，包含更多信息
                    display_name = f"{i}. {group['name']}"
                    if group['username']:
                        display_name += f" (@{group['username']})"
                    if group['is_verified']:
                        display_name += " ✓"
                    if group['is_scam']:
                        display_name += " ⚠️"
                    if group['is_fake']:
                        display_name += " ❌"
                    
                    self.log_message(f"  • {display_name}")
                    self.log_message(f"    ID: {group['id']}, 类型: {group['type']}, 成员: {group['member_count']}")
                    if group['description'] and group['description'] != "无描述":
                        self.log_message(f"    描述: {group['description'][:50]}...")
                    if group['unread_count'] > 0:
                        self.log_message(f"    未读消息: {group['unread_count']}")
                    self.log_message("")
                
                # 在群聊列表最后显示统计信息
                self.log_message("=" * 50)
                self.log_message(f"获取完成")
                self.log_message(f"📊 统计结果：")
                self.log_message(f"• 总群组数量：{len(groups)} 个")
                can_send = sum(1 for g in groups if g['entity'] is not None)
                self.log_message(f"• 可发送消息：{can_send} 个")
                self.log_message(f"• 不可发送：{len(groups) - can_send} 个")
                self.log_message("=" * 50)
            
        except Exception as error:
            error_msg = str(error)
            self.root.after(0, lambda: self.log_message(f"获取群组错误: {error_msg}"))

    def update_group_count(self, count):
        """更新群组数量显示"""
        # 计算选中的群组数量
        selected_count = 0
        for item in self.contact_tree.get_children():
            values = self.contact_tree.item(item)['values']
            if values[0] == "☑" and values[4] == "群组":  # 检查勾选框状态且类型为群组
                selected_count += 1
        
        self.group_count_label.config(text=f"群组数量: {selected_count}/{count}")
        
    def import_group_links(self):
        """导入群组链接文件"""
        if not self.is_connected:
            messagebox.showerror("错误", "请先连接Telegram")
            return
            
        # 禁用导入按钮，避免重复操作
        self.import_links_button.config(state=tk.DISABLED)
        
        # 选择文件
        file_path = filedialog.askopenfilename(
            title="选择群组链接文件",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        
        if not file_path:
            # 如果用户取消选择文件，重新启用按钮
            self.import_links_button.config(state=tk.NORMAL)
            return
            
        try:
            # 读取文件内容
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read().strip()
                
            if not content:
                messagebox.showerror("错误", "文件内容为空")
                return
                
            # 解析群组链接
            group_links = self.parse_group_links(content)
            
            if not group_links:
                messagebox.showerror("错误", "未找到有效的群组链接")
                return
                
            # 验证群组链接
            valid_links = self.validate_group_links(group_links)
            
            if not valid_links:
                messagebox.showerror("错误", "所有群组链接都无效")
                # 重新启用导入按钮
                self.import_links_button.config(state=tk.NORMAL)
                return
                
            # 保存有效的群组链接
            self.imported_group_links = valid_links
            
            # 显示导入结果
            self.log_message(f"✅ 成功导入 {len(valid_links)} 个群组链接")
            self.log_message(f"📋 群组链接列表:")
            for i, link in enumerate(valid_links, 1):
                self.log_message(f"  {i}. {link}")
            
            # 询问是否开始加入群组
            if messagebox.askyesno("确认", f"是否开始加入这 {len(valid_links)} 个群组？\n\n注意：加入群组操作需要谨慎，建议：\n1. 不要频繁操作\n2. 遵守群组规则\n3. 避免被检测为机器人"):
                self.start_join_groups()
            else:
                # 如果用户选择不开始加入，重新启用导入按钮
                self.import_links_button.config(state=tk.NORMAL)
                
        except Exception as e:
            messagebox.showerror("错误", f"导入文件失败: {e}")
            self.log_message(f"❌ 导入文件失败: {e}")
            # 发生错误时重新启用导入按钮
            self.import_links_button.config(state=tk.NORMAL)
            
    def parse_group_links(self, content):
        """解析群组链接字符串"""
        # 首先按换行符分割，这是最常见的格式
        lines = content.split('\n')
        links = []
        
        for line in lines:
            line = line.strip()
            if not line:
                continue
                
            # 如果一行中有多个链接（用逗号、顿号、空格等分隔），进一步分割
            if any(sep in line for sep in [',', '，', '、', ' ', '\t']):
                # 支持多种分隔符：逗号、顿号、空格、制表符
                separators = [',', '，', '、', ' ', '\t']
                for sep in separators:
                    if sep in line:
                        sub_links = [link.strip() for link in line.split(sep) if link.strip()]
                        links.extend(sub_links)
                        break
            else:
                # 单个链接
                links.append(line)
        
        # 去重并返回
        return list(dict.fromkeys(links))  # 保持顺序的去重
        
    def validate_group_links(self, group_links):
        """验证群组链接的有效性"""
        valid_links = []
        
        for link in group_links:
            # 清理链接，移除多余的空格
            link = link.strip()
            
            # 检查是否为Telegram群组链接格式
            if self.is_valid_telegram_link(link):
                valid_links.append(link)
            else:
                self.log_message(f"❌ 群组链接 {link} 格式无效")
                
        return valid_links
        
    def is_valid_telegram_link(self, link):
        """检查是否为有效的Telegram群组链接"""
        # Telegram群组链接的常见格式
        valid_patterns = [
            r'^https?://t\.me/[\w_]+$',  # https://t.me/groupname
            r'^https?://telegram\.me/[\w_]+$',  # https://telegram.me/groupname
            r'^@[\w_]+$',  # @groupname
            r'^https?://t\.me/joinchat/[\w-]+$',  # 私密群组链接
            r'^https?://telegram\.me/joinchat/[\w-]+$',  # 私密群组链接
        ]
        
        import re
        for pattern in valid_patterns:
            if re.match(pattern, link):
                return True
                
        return False
        
    def start_join_groups(self):
        """开始加入群组"""
        import random
        if not self.imported_group_links:
            messagebox.showerror("错误", "没有可加入的群组链接")
            return
        if self.is_joining_groups:
            messagebox.showwarning("警告", "正在加入群组中，请等待完成")
            return
        self.import_links_button.config(state=tk.NORMAL)
        self.is_joining_groups = True
        self.update_join_buttons_state()
        # 读取自定义参数（区间/单值）
        def parse_range(var_min, var_max, name, is_int=True):
            vmin = var_min.get().strip()
            vmax = var_max.get().strip()
            if vmin == '' and vmax == '':
                messagebox.showerror("错误", f"请填写{name}至少一个数值")
                return None
            try:
                if vmin != '' and vmax != '':
                    nmin = int(vmin) if is_int else float(vmin)
                    nmax = int(vmax) if is_int else float(vmax)
                    if nmin >= nmax:
                        messagebox.showerror("错误", f"{name}左侧数值必须小于右侧数值！")
                        var_min.set("")
                        return None
                    return (nmin, nmax)
                elif vmin != '':
                    nmin = int(vmin) if is_int else float(vmin)
                    return (nmin, nmin)
                else:
                    nmax = int(vmax) if is_int else float(vmax)
                    return (nmax, nmax)
            except Exception:
                messagebox.showerror("错误", f"{name}请输入有效的数值")
                return None
        # 读取参数
        self._join_interval_range = parse_range(self.join_interval_var_min, self.join_interval_var_max, "每隔(秒)", is_int=False)
        self._join_batch_size_range = parse_range(self.join_batch_size_var_min, self.join_batch_size_var_max, "每组(条)", is_int=True)
        self._join_batch_pause_range = parse_range(self.join_batch_pause_var_min, self.join_batch_pause_var_max, "组间休息(分钟)", is_int=False)
        if not all([self._join_interval_range, self._join_batch_size_range, self._join_batch_pause_range]):
            self.is_joining_groups = False
            self.update_join_buttons_state()
            return
        if hasattr(self, 'async_thread') and self.async_thread.is_alive():
            self.root.after(100, self._schedule_join_groups)
        else:
            messagebox.showerror("错误", "异步线程未运行")
            self.is_joining_groups = False
            self.update_join_buttons_state()

    async def _do_join_groups(self):
        """执行加入群组操作，支持自定义分组和间隔"""
        import re, random
        try:
            self.log_message("🚀 开始批量加入群组...")
            self.log_message(f"📊 总共需要加入 {len(self.imported_group_links)} 个群组")
            success_count = 0
            failed_count = 0
            links = self.imported_group_links
            # 取参数区间
            interval_range = getattr(self, '_join_interval_range', (5.0, 5.0))
            batch_size_range = getattr(self, '_join_batch_size_range', (5, 5))
            batch_pause_range = getattr(self, '_join_batch_pause_range', (3.0, 3.0))
            # 分组
            def get_batch_size():
                if batch_size_range[0] == batch_size_range[1]:
                    return batch_size_range[0]
                return random.randint(batch_size_range[0], batch_size_range[1])
            batches = []
            idx = 0
            total = len(links)
            while idx < total:
                size = get_batch_size()
                if size <= 0:
                    size = 1
                batches.append(links[idx:idx+size])
                idx += size
            for batch_idx, batch in enumerate(batches, 1):
                self.log_message(f"🟦 开始第{batch_idx}组（共{len(batch)}条）")
                for i, group_link in enumerate(batch, 1):
                    # 检查暂停状态
                    while self.pause_joining_groups and self.is_joining_groups:
                        await asyncio.sleep(0.5)
                    if not self.is_joining_groups:
                        self.log_message("⏹️ 用户取消了加入群组操作")
                        break
                    try:
                        self.log_message(f"🔄 [组{batch_idx}第{i}/{len(batch)}] 正在加入群组 {group_link}...")
                        result, error_msg = await self.join_single_group_by_link(group_link)
                        if result:
                            success_count += 1
                            self.log_message(f"✅ 成功加入群组 {group_link}")
                        else:
                            failed_count += 1
                            # 检查是否是FloodWait错误
                            wait_match = re.search(r"A wait of (\d+) seconds is required", error_msg or "")
                            if wait_match:
                                wait_seconds = int(wait_match.group(1)) + 5
                                if self.wait_flood_var.get():
                                    self.log_message(f"⏳ 检测到频繁操作，自动等待 {wait_seconds} 秒后继续...")
                                    for sec in range(wait_seconds):
                                        if not self.is_joining_groups:
                                            self.log_message("⏹️ 用户取消了加入群组操作")
                                            break
                                        await asyncio.sleep(1)
                                    if not self.is_joining_groups:
                                        return
                                    continue  # 继续下一个群组
                                else:
                                    self.log_message(f"❌ 检测到频繁操作，未勾选'频繁等待'，终止后续加群任务。")
                                    self.is_joining_groups = False
                                    return
                            else:
                                self.log_message(f"❌ 加入群组 {group_link} 失败，错误信息: {error_msg}")
                    except Exception as e:
                        failed_count += 1
                        self.log_message(f"❌ 加入群组 {group_link} 出错: {e}")
                    # 每条间隔
                    if i < len(batch) and self.is_joining_groups:
                        # 动态取间隔
                        if interval_range[0] == interval_range[1]:
                            interval = interval_range[0]
                        else:
                            interval = random.uniform(interval_range[0], interval_range[1])
                        self.log_message(f"⏳ 等待{interval:.2f}秒后继续...")
                        await asyncio.sleep(interval)
                # 每组间隔
                if batch_idx < len(batches) and self.is_joining_groups:
                    # 动态取组间休息
                    if batch_pause_range[0] == batch_pause_range[1]:
                        batch_pause = batch_pause_range[0]
                    else:
                        batch_pause = random.uniform(batch_pause_range[0], batch_pause_range[1])
                    self.log_message(f"⏸️ 第{batch_idx}组完成，等待{batch_pause:.2f}分钟后继续下一组...")
                    await asyncio.sleep(batch_pause * 60)
            self.log_message("🎉 批量加入群组完成！")
            self.log_message(f"📈 成功: {success_count} 个")
            self.log_message(f"📉 失败: {failed_count} 个")
            self.log_message(f"📊 成功率: {success_count/(success_count+failed_count)*100:.1f}%" if (success_count+failed_count) > 0 else "📊 成功率: 0%")
        except Exception as e:
            self.log_message(f"❌ 批量加入群组过程中出错: {e}")
        finally:
            self.is_joining_groups = False
            self.root.after(0, self.update_join_buttons_state)

    def cancel_join_groups(self):
        """取消加入群组"""
        if not self.is_joining_groups:
            messagebox.showinfo("提示", "当前没有正在进行的加入群组操作")
            return
            
        if messagebox.askyesno("确认", "确定要取消当前的加入群组操作吗？"):
            self.is_joining_groups = False
            self.log_message("⏹️ 用户取消了加入群组操作")
            self.update_join_buttons_state()
            
    def toggle_pause_join(self):
        """暂停/开始加群任务"""
        if not self.is_joining_groups:
            return
        if not self.pause_joining_groups:
            self.pause_joining_groups = True
            self.pause_join_button.config(text="开始")
            self.log_message("⏸️ 加群任务已暂停")
        else:
            self.pause_joining_groups = False
            self.pause_join_button.config(text="暂停")
            self.log_message("▶️ 加群任务已恢复")
            
    def update_join_buttons_state(self):
        """更新加入群组相关按钮状态"""
        if self.is_joining_groups:
            self.cancel_join_button.config(state=tk.NORMAL)
            self.pause_join_button.config(state=tk.NORMAL)
            if self.pause_joining_groups:
                self.pause_join_button.config(text="开始")
            else:
                self.pause_join_button.config(text="暂停")
        else:
            self.cancel_join_button.config(state=tk.DISABLED)
            self.pause_join_button.config(state=tk.DISABLED)
        
    def update_contact_list(self):
        """更新联系人列表显示"""
        # 清空现有项目
        for item in self.contact_tree.get_children():
            self.contact_tree.delete(item)
            
        # 添加联系人
        for contact in self.contacts:
            self.contact_tree.insert('', 'end', values=(
                "☐",  # 勾选框，默认未选中
                contact['name'],
                contact['username'],
                contact['phone'],
                contact['type'],
                contact['id']  # 添加ID列
            ))
            
        # 添加群组，保存ID信息
        for group in self.groups:
            self.contact_tree.insert('', 'end', values=(
                "☐",  # 勾选框，默认未选中
                group['name'],
                group['username'],
                group['phone'],
                group['type'],
                group['id']  # 添加ID列
            ))
            
    def delete_selected(self):
        """删除勾选的联系人"""
        checked_items = []
        for item in self.contact_tree.get_children():
            values = self.contact_tree.item(item)['values']
            if values[0] == "☑":  # 检查勾选框状态
                checked_items.append(item)
                
        if not checked_items:
            messagebox.showwarning("警告", "请先勾选要删除的联系人")
            return
            
        if messagebox.askyesno("确认", f"确定要删除勾选的 {len(checked_items)} 个联系人吗？"):
            for item in checked_items:
                values = self.contact_tree.item(item)['values']
                # 从列表中移除（注意索引偏移）
                self.contacts = [c for c in self.contacts if not (
                    c['name'] == values[1] and c['username'] == values[2]
                )]
                self.groups = [g for g in self.groups if not (
                    g['name'] == values[1] and g['username'] == values[2]
                )]
                
            self.update_contact_list()
            self.log_message(f"删除了 {len(checked_items)} 个联系人")
            
    def select_all(self):
        """全选"""
        for item in self.contact_tree.get_children():
            values = list(self.contact_tree.item(item)['values'])
            values[0] = "☑"
            self.contact_tree.item(item, values=values)
            
        # 更新群组数量显示
        if hasattr(self, 'groups'):
            self.update_group_count(len(self.groups))
            
    def unselect_all(self):
        """取消全选"""
        for item in self.contact_tree.get_children():
            values = list(self.contact_tree.item(item)['values'])
            values[0] = "☐"
            self.contact_tree.item(item, values=values)
            
        # 更新群组数量显示
        if hasattr(self, 'groups'):
            self.update_group_count(len(self.groups))
        
    async def send_message(self, message):
        """发送消息到选中的联系人/群组"""
        import random  # 添加random模块导入
        if not self.is_connected:
            messagebox.showerror("错误", "请先连接Telegram")
            return
            
        if not message.strip():
            messagebox.showerror("错误", "请输入要发送的消息")
            return
            
        # 获取勾选的项目
        checked_items = []
        for item in self.contact_tree.get_children():
            values = self.contact_tree.item(item)['values']
            if values[0] == "☑":  # 检查勾选框状态
                checked_items.append(item)
                
        if not checked_items:
            messagebox.showerror("错误", "请勾选要发送的联系人或群组")
            return
            
        # 设置发送状态
        self.is_sending = True
        self.cancel_sending = False
        
        # 更新按钮状态
        self.root.after(0, lambda: self.send_button.config(state=tk.DISABLED))
        self.root.after(0, lambda: self.cancel_button.config(state=tk.NORMAL))
            
        # 收集本次发送的日志
        send_logs = []
        send_logs.append(f"开始发送消息: {message}")
        send_logs.append(f"勾选项目数量: {len(checked_items)}")
        send_logs.append("-" * 50)
        
        # 同时在控制台打印
        self.log_message(f"开始发送消息: {message}")
        self.log_message(f"勾选项目数量: {len(checked_items)}")
        self.log_message("-" * 50)
        
        success_count = 0
        failed_count = 0
        
        # 获取发送间隔时间
        try:
            interval_min = float(self.interval_var_min.get().strip() or "0")
            interval_max = float(self.interval_var_max.get().strip() or interval_min)
            if interval_min < 0:
                interval_min = 0
            if interval_max < interval_min:
                interval_max = interval_min
        except ValueError:
            interval_min = interval_max = 2.0  # 默认2秒
            
        self.log_message(f"发送间隔设置为: {interval_min if interval_min == interval_max else f'{interval_min}-{interval_max}'} 秒")
        send_logs.append(f"发送间隔: {interval_min if interval_min == interval_max else f'{interval_min}-{interval_max}'} 秒")
        
        for i, item in enumerate(checked_items):
            item_data = self.contact_tree.item(item)['values']
            name = item_data[1]  # 由于添加了勾选框列，索引需要+1
            username = item_data[2]
            contact_type = item_data[4]
            entity_id = item_data[5]
            
            # 显示发送进度
            progress_msg = f"[{i+1}/{len(checked_items)}] 准备发送到: {name} ({contact_type})"
            send_logs.append(progress_msg)
            self.log_message(progress_msg)
            
            # 如果不是第一个消息，添加发送间隔
            if i > 0:
                # 在区间内随机取值
                if interval_min == interval_max:
                    interval = interval_min
                else:
                    interval = random.uniform(interval_min, interval_max)
                    
                if interval > 0:
                    wait_msg = f"等待 {interval:.2f} 秒后发送下一条消息..."
                    self.log_message(wait_msg)
                    send_logs.append(f"等待 {interval:.2f} 秒...")
                    
                    # 在等待期间检查取消状态
                    for _ in range(int(interval * 10)):  # 每0.1秒检查一次
                        if self.cancel_sending:
                            self.log_message("发送已取消")
                            send_logs.append("发送已取消")
                            # 重置发送状态
                            self.is_sending = False
                            self.cancel_sending = False
                            
                            # 恢复按钮状态
                            self.root.after(0, lambda: self.send_button.config(state=tk.NORMAL))
                            self.root.after(0, lambda: self.cancel_button.config(state=tk.DISABLED))
                            
                            # 显示取消结果
                            result_message = f"发送已取消！\n成功: {success_count} 个\n失败: {failed_count} 个"
                            if success_count > 0:
                                messagebox.showinfo("发送结果", result_message)
                            
                            # 询问是否打印日志
                            log_content = "\n".join(send_logs)
                            self.root.after(0, lambda: self.show_print_log_dialog(log_content))
                            return
                        await asyncio.sleep(0.1)
            
            # 直接使用ID发送消息，不再通过名称匹配
            try:
                if contact_type in ['群组', '频道']:
                    # 对于群组/频道，直接使用ID发送
                    send_logs.append(f"尝试发送到群组: {name}, ID: {entity_id}")
                    self.log_message(f"尝试发送到群组: {name}, ID: {entity_id}")
                    
                    # 方法1：直接使用ID发送
                    try:
                        send_logs.append("方法1: 直接使用ID发送")
                        self.log_message("方法1: 直接使用ID发送")
                        await self.client.send_message(int(entity_id), message)
                        send_logs.append(f"✅ 方法1成功: {name}")
                        self.log_message(f"✅ 方法1成功: {name}")
                        success_count += 1
                        continue
                    except Exception as e1:
                        send_logs.append(f"❌ 方法1失败: {e1}")
                        self.log_message(f"❌ 方法1失败: {e1}")
                        
                        # 方法2：通过get_entity获取实体
                        try:
                            send_logs.append("方法2: 通过get_entity获取实体")
                            self.log_message("方法2: 通过get_entity获取实体")
                            entity = await self.client.get_entity(int(entity_id))
                            await self.client.send_message(entity, message)
                            send_logs.append(f"✅ 方法2成功: {name}")
                            self.log_message(f"✅ 方法2成功: {name}")
                            success_count += 1
                            continue
                        except Exception as e2:
                            send_logs.append(f"❌ 方法2失败: {e2}")
                            self.log_message(f"❌ 方法2失败: {e2}")
                            
                            # 方法3：通过get_dialogs查找
                            try:
                                send_logs.append("方法3: 通过get_dialogs查找")
                                self.log_message("方法3: 通过get_dialogs查找")
                                async for dialog in self.client.iter_dialogs():
                                    if dialog.id == int(entity_id):
                                        await self.client.send_message(dialog, message)
                                        send_logs.append(f"✅ 方法3成功: {name}")
                                        self.log_message(f"✅ 方法3成功: {name}")
                                        success_count += 1
                                        break
                                else:
                                    send_logs.append(f"❌ 方法3失败: 未找到对话")
                                    self.log_message(f"❌ 方法3失败: 未找到对话")
                                    failed_count += 1
                            except Exception as e3:
                                send_logs.append(f"❌ 方法3失败: {e3}")
                                self.log_message(f"❌ 方法3失败: {e3}")
                                failed_count += 1
                                
                else:  # 用户
                    send_logs.append(f"尝试发送到用户: {name}, ID: {entity_id}")
                    self.log_message(f"尝试发送到用户: {name}, ID: {entity_id}")
                    try:
                        await self.client.send_message(int(entity_id), message)
                        send_logs.append(f"✅ 发送成功: {name}")
                        self.log_message(f"✅ 发送成功: {name}")
                        success_count += 1
                    except Exception as e:
                        send_logs.append(f"❌ 发送失败: {e}")
                        self.log_message(f"❌ 发送失败: {e}")
                        failed_count += 1
                        
            except Exception as e:
                send_logs.append(f"❌ 发送异常: {e}")
                self.log_message(f"❌ 发送异常: {e}")
                failed_count += 1
        
        # 发送完成后的统计
        if self.cancel_sending:
            send_logs.append("-" * 50)
            send_logs.append(f"发送已取消 - 成功: {success_count}, 失败: {failed_count}")
            self.log_message("-" * 50)
            self.log_message(f"发送已取消 - 成功: {success_count}, 失败: {failed_count}")
        else:
            send_logs.append("-" * 50)
            send_logs.append(f"发送完成 - 成功: {success_count}, 失败: {failed_count}")
            self.log_message("-" * 50)
            self.log_message(f"发送完成 - 成功: {success_count}, 失败: {failed_count}")
            
            # 只有在正常完成时才清空勾选框
            for item in checked_items:
                values = list(self.contact_tree.item(item)['values'])
                values[0] = "☐"  # 重置为未选中状态
                self.contact_tree.item(item, values=values)
        
        # 重置发送状态
        self.is_sending = False
        self.cancel_sending = False
        
        # 恢复按钮状态
        self.root.after(0, lambda: self.send_button.config(state=tk.NORMAL))
        self.root.after(0, lambda: self.cancel_button.config(state=tk.DISABLED))
        
        # 显示发送结果
        result_message = f"发送完成！\n成功: {success_count} 个\n失败: {failed_count} 个"
        if success_count > 0:
            messagebox.showinfo("发送结果", result_message)
        else:
            messagebox.showerror("发送失败", result_message)
        
        # 询问是否打印日志
        log_content = "\n".join(send_logs)
        self.root.after(0, lambda: self.show_print_log_dialog(log_content))
        
    def cancel_send(self):
        """取消发送"""
        self.cancel_sending = True
        self.log_message("用户请求取消发送，正在停止...")
        
    def schedule_send(self):
        """定时发送"""
        # 检查许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        # if not is_valid:
        #     messagebox.showerror("许可证验证", f"许可证无效：{message}\n请重新激活软件。")
        #     if not self.verify_license():
        #         return
        
        # 移除单任务限制，支持多任务
            
        # 获取勾选的项目
        checked_items = []
        for item in self.contact_tree.get_children():
            values = self.contact_tree.item(item)['values']
            if values[0] == "☑":  # 检查勾选框状态
                checked_items.append(item)
                
        if not checked_items:
            messagebox.showerror("错误", "请勾选要发送的联系人或群组")
            return
            
        # 获取消息内容
        message = self.message_text.get("1.0", tk.END).strip()
        if not message:
            messagebox.showerror("错误", "请输入要发送的消息")
            return
            
        # 获取定时分钟数
        try:
            minutes = float(self.schedule_minutes_var.get())
            if minutes <= 0:
                messagebox.showerror("错误", "定时时间必须大于0分钟")
                return
        except ValueError:
            messagebox.showerror("错误", "请输入有效的分钟数")
            return
            
        # 获取发送间隔时间
        try:
            interval_min = float(self.interval_var_min.get().strip() or "0")
            interval_max = float(self.interval_var_max.get().strip() or interval_min)
            if interval_min < 0:
                interval_min = 0
            if interval_max < interval_min:
                interval_max = interval_min
        except ValueError:
            interval_min = interval_max = 2.0  # 默认2秒
            
        # 计算发送时间
        from datetime import datetime, timedelta
        send_time = datetime.now() + timedelta(minutes=minutes)
        
        # 生成任务ID
        import uuid
        task_id = str(uuid.uuid4())[:8]
        
        # 创建定时任务
        task = {
            'id': task_id,
            'message': message,
            'checked_items': checked_items.copy(),
            'send_time': send_time,
            'minutes': minutes,
            'interval_min': interval_min,  # 保存最小间隔
            'interval_max': interval_max,  # 保存最大间隔
            'status': '等待中'
        }
        
        self.scheduled_tasks.append(task)
        self.is_scheduling = True
        
        # 更新定时任务列表
        self.add_schedule_task_to_list(task)
        
        # 记录日志
        self.log_message(f"已添加定时发送任务 [{task_id}]，将在 {minutes} 分钟后发送")
        self.log_message(f"发送时间: {send_time.strftime('%Y-%m-%d %H:%M:%S')}")
        self.log_message(f"目标群组数量: {len(checked_items)}")
        
        # 启动定时任务
        self.start_schedule_task(task)
        
    def cancel_schedule_send(self):
        """取消定时发送"""
        # 获取选中的定时任务
        checked_tasks = []
        for item in self.schedule_tree.get_children():
            values = self.schedule_tree.item(item)['values']
            if values[0] == "☑":  # 检查勾选框状态
                task_id = self.schedule_tree.item(item)['tags'][0]  # 获取任务ID
                checked_tasks.append(task_id)
                
        if not checked_tasks:
            messagebox.showwarning("警告", "请先选择要取消的定时任务")
            return
            
        # 确认取消
        if not messagebox.askyesno("确认", f"确定要取消选中的 {len(checked_tasks)} 个定时任务吗？"):
            return
            
        # 取消选中的任务
        cancelled_count = 0
        for task_id in checked_tasks:
            # 从任务列表中移除
            self.scheduled_tasks = [task for task in self.scheduled_tasks if task['id'] != task_id]
            # 从界面列表中移除
            self.remove_schedule_task_from_list(task_id)
            cancelled_count += 1
            
        # 检查是否还有其他任务
        if not self.scheduled_tasks:
            self.is_scheduling = False
            
        # 更新按钮状态
        self.update_schedule_button_state()
        
        self.log_message(f"已取消 {cancelled_count} 个定时发送任务")
        
    def add_schedule_task_to_list(self, task):
        """添加定时任务到列表"""
        # 截断消息内容用于显示
        message_preview = task['message'][:30] + "..." if len(task['message']) > 30 else task['message']
        
        # 添加到列表
        self.schedule_tree.insert('', 'end', values=(
            "☐",  # 勾选框
            task['send_time'].strftime('%H:%M:%S'),  # 发送时间
            message_preview,  # 消息内容预览
            f"{len(task['checked_items'])}个",  # 目标群组数量
            task['status']  # 状态
        ), tags=(task['id'],))  # 使用任务ID作为标签
        
    def update_schedule_task_status(self, task_id, status):
        """更新定时任务状态"""
        for item in self.schedule_tree.get_children():
            if task_id in self.schedule_tree.item(item)['tags']:
                values = list(self.schedule_tree.item(item)['values'])
                values[4] = status  # 更新状态列
                self.schedule_tree.item(item, values=values)
                break
                
    def remove_schedule_task_from_list(self, task_id):
        """从列表中移除定时任务"""
        for item in self.schedule_tree.get_children():
            if task_id in self.schedule_tree.item(item)['tags']:
                self.schedule_tree.delete(item)
                break
        
    def start_schedule_task(self, task):
        """启动定时任务"""
        import threading
        import time
        from datetime import datetime
        
        def schedule_worker():
            # 计算等待时间
            wait_seconds = (task['send_time'] - datetime.now()).total_seconds()
            
            if wait_seconds > 0:
                # 等待到指定时间
                time.sleep(wait_seconds)
                
            # 检查任务是否被取消
            if task not in self.scheduled_tasks:
                return
                
            # 执行发送
            self.root.after(0, lambda: self.execute_scheduled_send(task))
            
        # 启动后台线程
        thread = threading.Thread(target=schedule_worker, daemon=True)
        thread.start()
        
    def execute_scheduled_send(self, task):
        """执行定时发送"""
        if task not in self.scheduled_tasks:
            return
            
        # 更新任务状态
        self.update_schedule_task_status(task['id'], '发送中')
        
        # 记录开始发送
        self.log_message(f"定时发送任务 [{task['id']}] 开始执行")
        
        # 使用已保存的事件循环执行发送
        if self.async_loop and not self.async_loop.is_closed():
            future = asyncio.run_coroutine_threadsafe(self.send_scheduled_message(task), self.async_loop)
        else:
            self.log_message("错误: 事件循环不可用")
            self.update_schedule_task_status(task['id'], '发送失败')
            
    async def send_scheduled_message(self, task):
        """发送定时消息"""
        import random  # 添加random模块导入
        message = task['message']
        checked_items = task['checked_items']
        
        # 收集本次发送的日志
        send_logs = []
        send_logs.append(f"定时发送开始 - 消息: {message}")
        send_logs.append(f"勾选项目数量: {len(checked_items)}")
        send_logs.append("-" * 50)
        
        # 同时在控制台打印
        self.log_message(f"定时发送开始 - 消息: {message}")
        self.log_message(f"勾选项目数量: {len(checked_items)}")
        self.log_message("-" * 50)
        
        success_count = 0
        failed_count = 0
        
        # 从任务对象获取发送间隔时间
        try:
            interval_min = float(self.interval_var_min.get().strip() or "0")
            interval_max = float(self.interval_var_max.get().strip() or interval_min)
            if interval_min < 0:
                interval_min = 0
            if interval_max < interval_min:
                interval_max = interval_min
        except ValueError:
            interval_min = interval_max = 2.0  # 默认2秒
            
        self.log_message(f"发送间隔设置为: {interval_min if interval_min == interval_max else f'{interval_min}-{interval_max}'} 秒")
        send_logs.append(f"发送间隔: {interval_min if interval_min == interval_max else f'{interval_min}-{interval_max}'} 秒")
        
        for i, item in enumerate(checked_items):
            item_data = self.contact_tree.item(item)['values']
            name = item_data[1]  # 由于添加了勾选框列，索引需要+1
            username = item_data[2]
            contact_type = item_data[4]
            entity_id = item_data[5]
            
            # 显示发送进度
            progress_msg = f"[{i+1}/{len(checked_items)}] 定时发送到: {name} ({contact_type})"
            send_logs.append(progress_msg)
            self.log_message(progress_msg)
            
            # 如果不是第一个消息，添加发送间隔
            if i > 0:
                # 在区间内随机取值
                if interval_min == interval_max:
                    interval = interval_min
                else:
                    interval = random.uniform(interval_min, interval_max)
                    
                if interval > 0:
                    wait_msg = f"等待 {interval:.2f} 秒后发送下一条消息..."
                    self.log_message(wait_msg)
                    send_logs.append(f"等待 {interval:.2f} 秒...")
                    await asyncio.sleep(interval)
            
            # 发送消息
            try:
                if contact_type in ['群组', '频道']:
                    try:
                        await self.client.send_message(int(entity_id), message)
                        send_logs.append(f"✅ 定时发送成功: {name}")
                        self.log_message(f"✅ 定时发送成功: {name}")
                        success_count += 1
                    except Exception as e:
                        send_logs.append(f"❌ 定时发送失败: {name} - {e}")
                        self.log_message(f"❌ 定时发送失败: {name} - {e}")
                        failed_count += 1
                else:  # 用户
                    try:
                        await self.client.send_message(int(entity_id), message)
                        send_logs.append(f"✅ 定时发送成功: {name}")
                        self.log_message(f"✅ 定时发送成功: {name}")
                        success_count += 1
                    except Exception as e:
                        send_logs.append(f"❌ 定时发送失败: {name} - {e}")
                        self.log_message(f"❌ 定时发送失败: {name} - {e}")
                        failed_count += 1
                        
            except Exception as e:
                send_logs.append(f"❌ 定时发送异常: {e}")
                self.log_message(f"❌ 定时发送异常: {e}")
                failed_count += 1
        
        # 发送完成后的统计
        send_logs.append("-" * 50)
        send_logs.append(f"定时发送完成 - 成功: {success_count}, 失败: {failed_count}")
        self.log_message("-" * 50)
        self.log_message(f"定时发送完成 - 成功: {success_count}, 失败: {failed_count}")
        
        # 更新任务状态并移除已完成的任务
        task_id = task['id']
        if success_count > 0:
            self.update_schedule_task_status(task_id, '已完成')
            self.log_message(f"定时任务 [{task_id}] 已完成")
        else:
            self.update_schedule_task_status(task_id, '发送失败')
            self.log_message(f"定时任务 [{task_id}] 发送失败")
            
        # 延迟移除任务，让用户看到状态
        self.root.after(3000, lambda: self.complete_schedule_task(task_id))
        
        # 显示发送结果
        result_message = f"定时发送完成！\n成功: {success_count} 个\n失败: {failed_count} 个"
        if success_count > 0:
            messagebox.showinfo("定时发送结果", result_message)
        else:
            messagebox.showerror("定时发送失败", result_message)
        
        # 询问是否打印日志
        log_content = "\n".join(send_logs)
        self.root.after(0, lambda: self.show_print_log_dialog(log_content))
        
    def complete_schedule_task(self, task_id):
        """完成定时任务（移除任务）"""
        # 从任务列表中移除
        self.scheduled_tasks = [task for task in self.scheduled_tasks if task['id'] != task_id]
        # 从界面列表中移除
        self.remove_schedule_task_from_list(task_id)
        
        # 检查是否还有其他任务
        if not self.scheduled_tasks:
            self.is_scheduling = False
            
        # 更新按钮状态
        self.update_schedule_button_state()
        
    def send_messages(self):
        """发送消息的包装方法"""
        # 检查许可证（暂时跳过）
        # is_valid, message = self.license_manager.is_license_valid()
        # if not is_valid:
        #     messagebox.showerror("许可证验证", f"许可证无效：{message}\n请重新激活软件。")
        #     if not self.verify_license():
        #         return
        
        if self.is_sending:
            messagebox.showwarning("警告", "正在发送中，请等待完成或点击取消发送")
            return
            
        message = self.message_text.get("1.0", tk.END).strip()
        if not message:
            messagebox.showerror("错误", "请输入要发送的消息")
            return
            
        # 使用已保存的事件循环
        if self.async_loop and not self.async_loop.is_closed():
            future = asyncio.run_coroutine_threadsafe(self.send_message(message), self.async_loop)
        else:
            messagebox.showerror("错误", "事件循环不可用")
                
    def export_logs(self):
        """导出当前操作日志内容为txt文件"""
        from tkinter import filedialog, messagebox
        filename = filedialog.asksaveasfilename(
            title="保存日志文件",
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if filename:
            try:
                with open(filename, 'w', encoding='utf-8') as f:
                    f.write(self.log_text.get("1.0", tk.END))
                self.log_message(f"成功导出日志到 {filename}")
            except Exception as e:
                messagebox.showerror("错误", f"导出日志失败: {str(e)}")
                
    def show_print_log_dialog(self, log_content):
        """显示是否打印日志的弹窗"""
        # 创建自定义弹窗
        dialog = tk.Toplevel(self.root)
        dialog.title("保存发送日志")
        dialog.geometry("400x200")
        dialog.resizable(False, False)
        dialog.transient(self.root)
        dialog.grab_set()
        
        # 设置弹窗样式
        dialog.configure(bg='#f0f0f0')
        
        # 居中显示
        dialog.update_idletasks()
        x = (dialog.winfo_screenwidth() // 2) - (400 // 2)
        y = (dialog.winfo_screenheight() // 2) - (200 // 2)
        dialog.geometry(f"400x200+{x}+{y}")
        
        # 主框架
        main_frame = tk.Frame(dialog, bg='#f0f0f0')
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
        
        # 图标和标题
        title_frame = tk.Frame(main_frame, bg='#f0f0f0')
        title_frame.pack(fill=tk.X, pady=(0, 15))
        
        # 使用Unicode图标
        icon_label = tk.Label(title_frame, text="📄", font=("Arial", 24), bg='#f0f0f0', fg='#2196F3')
        icon_label.pack(side=tk.LEFT, padx=(0, 10))
        
        title_label = tk.Label(title_frame, text="保存发送日志", 
                              font=("Arial", 16, "bold"), 
                              bg='#f0f0f0', fg='#333333')
        title_label.pack(side=tk.LEFT)
        
        # 提示文本
        desc_label = tk.Label(main_frame, 
                             text="是否将本次发送的详细日志保存到文件？\n保存后可在文件管理器中查看完整的发送记录。", 
                             font=("Arial", 11), 
                             bg='#f0f0f0', fg='#666666',
                             justify=tk.LEFT)
        desc_label.pack(pady=(0, 20))
        
        # 按钮框架
        button_frame = tk.Frame(main_frame, bg='#f0f0f0')
        button_frame.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 保存按钮（现代化设计）
        save_button = tk.Button(button_frame, 
                               text="💾 保存日志", 
                               font=("Arial", 12, "bold"),
                               bg='#4CAF50', fg='white',
                               activebackground='#45a049', activeforeground='white',
                               relief=tk.FLAT, bd=0,
                               width=12, height=1,
                               cursor='hand2',
                               command=lambda: self.print_logs(log_content, dialog))
        save_button.pack(side=tk.RIGHT, padx=(10, 0))
        
        # 取消按钮
        cancel_button = tk.Button(button_frame, 
                                 text="取消", 
                                 font=("Arial", 12),
                                 bg='#f8f9fa', fg='#6c757d',
                                 activebackground='#e9ecef', activeforeground='#6c757d',
                                 relief=tk.FLAT, bd=1, highlightthickness=1,
                                 highlightbackground='#dee2e6',
                                 width=8, height=1,
                                 cursor='hand2',
                                 command=dialog.destroy)
        cancel_button.pack(side=tk.RIGHT)
        
        # 设置焦点
        save_button.focus_set()
        
        # 绑定快捷键
        dialog.bind('<Return>', lambda e: self.print_logs(log_content, dialog))
        dialog.bind('<Escape>', lambda e: dialog.destroy())
        
        # 添加悬停效果
        def on_save_enter(e):
            save_button.configure(bg='#45a049')
            
        def on_save_leave(e):
            save_button.configure(bg='#4CAF50')
            
        def on_cancel_enter(e):
            cancel_button.configure(bg='#e9ecef')
            
        def on_cancel_leave(e):
            cancel_button.configure(bg='#f8f9fa')
            
        save_button.bind('<Enter>', on_save_enter)
        save_button.bind('<Leave>', on_save_leave)
        cancel_button.bind('<Enter>', on_cancel_enter)
        cancel_button.bind('<Leave>', on_cancel_leave)
    
    def print_logs(self, log_content, dialog):
        """打印日志"""
        try:
            # 保存日志到文件
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"发送日志_{timestamp}.txt"
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write(f"发送日志 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write("=" * 50 + "\n")
                f.write(log_content)
            
            self.log_message(f"✅ 日志已保存到: {filename}")
            messagebox.showinfo("成功", f"日志已保存到文件：\n{filename}")
            
        except Exception as e:
            self.log_message(f"❌ 保存日志失败: {e}")
            messagebox.showerror("错误", f"保存日志失败：{e}")
        
        dialog.destroy()
                
    def run(self):
        """运行应用程序"""
        self.root.mainloop()
        
    def cleanup(self):
        """清理资源"""
        # 停止许可证验证（暂时跳过）
        # if hasattr(self, 'license_manager'):
        #     self.license_manager.stop_periodic_verification()
        
        if self.client and self.is_connected:
            try:
                # 使用已保存的事件循环
                if self.async_loop and not self.async_loop.is_closed():
                    try:
                        future = asyncio.run_coroutine_threadsafe(self.client.disconnect(), self.async_loop)
                        # 等待断开连接完成
                        future.result(timeout=5)
                    except Exception as e:
                        # 忽略异步清理错误
                        pass
                else:
                    # 如果没有事件循环，直接断开
                    try:
                        self.client.disconnect()
                    except:
                        pass
                self.log_message("已断开Telegram连接")
            except Exception as e:
                # 忽略清理过程中的错误
                pass

    def _schedule_join_groups(self):
        """调度加入群组任务"""
        if self.async_loop and not self.async_loop.is_closed():
            future = asyncio.run_coroutine_threadsafe(self._do_join_groups(), self.async_loop)
        else:
            messagebox.showerror("错误", "事件循环不可用")
            self.is_joining_groups = False

    async def join_single_group_by_link(self, group_link):
        """通过链接加入单个群组"""
        try:
            if group_link.startswith('@'):
                username = group_link[1:]
                await self.client(functions.channels.JoinChannelRequest(channel=username))
            elif 'joinchat' in group_link:
                invite_hash = group_link.split('/')[-1]
                await self.client(functions.messages.ImportChatInviteRequest(hash=invite_hash))
            else:
                username = group_link.split('/')[-1]
                await self.client(functions.channels.JoinChannelRequest(channel=username))
            return True, None
        except Exception as e:
            error_msg = str(e)
            self.log_message(f"❌ 群组 {group_link} 加入失败: {error_msg}")
            return False, error_msg

    def stat_group_count(self):
        """导入txt，统计有效群组链接数量，过滤失效并导出新文件"""
        from tkinter import filedialog, messagebox
        import os, re
        # 选择要导入的txt文件
        file_path = filedialog.askopenfilename(
            title="选择包含群组链接的文本文件",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if not file_path:
            return
        # 读取内容
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                content = f.read()
        except Exception as e:
            messagebox.showerror("错误", f"读取文件失败: {e}")
            return
        # 解析所有链接，支持多种分隔符
        def parse_links(content):
            lines = content.split('\n')
            links = []
            for line in lines:
                line = line.strip()
                if not line:
                    continue
                for sep in [',', '，', '、', ' ', '\t']:
                    if sep in line:
                        sub_links = [l.strip() for l in line.split(sep) if l.strip()]
                        links.extend(sub_links)
                        break
                else:
                    links.append(line)
            return list(dict.fromkeys(links))  # 去重
        all_links = parse_links(content)
        # 只保留有效群组链接
        def is_valid_group_link(link):
            valid_patterns = [
                r'^https?://t\.me/[\w_]+$',
                r'^https?://telegram\.me/[\w_]+$',
                r'^@([\w_]+)$',
            ]
            for pattern in valid_patterns:
                if re.match(pattern, link):
                    return True
            return False
        valid_group_links = [l for l in all_links if is_valid_group_link(l)]
        group_count = len(valid_group_links)
        if group_count == 0:
            messagebox.showinfo("提示", "未识别到有效的群组链接！")
            return
        base_name = os.path.splitext(os.path.basename(file_path))[0]
        save_path = filedialog.asksaveasfilename(
            title=f"保存群组链接（共{group_count}个）",
            initialfile=f"{base_name}{group_count}个.txt",
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if save_path:
            try:
                with open(save_path, 'w', encoding='utf-8') as f:
                    f.write('\n'.join(valid_group_links))
                messagebox.showinfo("成功", f"群组链接已导出: {save_path}")
            except Exception as e:
                messagebox.showerror("错误", f"导出群组链接失败: {e}")

# 主函数直接运行Tk主循环

def main():
    app = None
    try:
        app = TelegramMassSender()
        
        # 在主线程中设置事件循环
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        # 启动Tkinter主循环
        app.run()
    except Exception as e:
        print(f"程序运行出错: {e}")
    finally:
        # 确保清理资源
        if app:
            app.cleanup()

if __name__ == "__main__":
    main()
